package org.dce.screencapturer;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;

import org.dce.screencapturer.objects.DrawingObject;

public class DragHandler 
	implements MouseListener, 
		MouseMotionListener, 
		ContainerListener{

	private Point pickPoint;
	private Drawing drawing;
	private BulkDrag bulkDrag = null;
	
	public DragHandler(Drawing drawing){
		this.drawing = drawing;
		this.drawing.addContainerListener(this);
	}


	@Override
	public void mousePressed(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON1
			&& e.getClickCount() == 1){
			Set<DrawingObject> selection = drawing.getSelectionHandler().getSelection();
			
			for (DrawingObject object:selection){
				if (!object.canDrag(e)){
					return;
				}
			}
			
			this.pickPoint = e.getPoint();
			this.bulkDrag = new BulkDrag(selection);
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		
		if (bulkDrag != null){
			commit();
		}
	}

	private void commit(){
		
		bulkDrag.captureNewPositions();
		drawing.getUI().getUndoManager().addEdit(bulkDrag);
		pickPoint = null;
		bulkDrag = null;
	}
	@Override
	public void mouseDragged(MouseEvent e) {
		
		if (bulkDrag != null){
			Point point = e.getPoint();
			
			int deltaX = point.x - pickPoint.x;
			int deltaY = point.y - pickPoint.y;
	
			for (DrawingObject object:bulkDrag.getObjects()){
				Point currentLocation = object.getLocation();
				object.setLocation(currentLocation.x + deltaX, currentLocation.y + deltaY);
			}
		}
	}

	public void mouseClicked(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	public void mouseMoved(MouseEvent e) {}

	@Override
	public void componentAdded(ContainerEvent e) {
		
		Component component = e.getChild();
		
		if (component instanceof DrawingObject){
			((DrawingObject)component).addMouseListener(this);
			((DrawingObject)component).addMouseMotionListener(this);
		}
	}

	@Override
	public void componentRemoved(ContainerEvent e) {
		Component component = e.getChild();
		
		if (component instanceof DrawingObject){
			((DrawingObject)component).removeMouseListener(this);
			((DrawingObject)component).removeMouseMotionListener(this);
		}
	}
	
	public class BulkDrag implements UndoableEdit {

		private Set<DrawingObject> objects;

		private Map<DrawingObject,Point> originalPositions = new HashMap<DrawingObject, Point>();
		private Map<DrawingObject,Point> newPositions = new HashMap<DrawingObject, Point>();
		private boolean undone = false;
		public BulkDrag(Set<DrawingObject> objects){
			this.objects = objects;
			for (DrawingObject object:objects){
				originalPositions.put(object, object.getLocation());
			}
		}

		public Set<DrawingObject> getObjects() {
			return objects;
		}
		
		public void captureNewPositions(){
			for (DrawingObject object:objects){
				newPositions.put(object, object.getLocation());
			}
		}
		
		@Override
		public boolean addEdit(UndoableEdit anEdit) {
			return false;
		}

		@Override
		public boolean canRedo() {
			return undone;
		}

		@Override
		public boolean canUndo() {
			return !undone;
		}

		@Override
		public void die() {
		}

		@Override
		public String getPresentationName() {
			return "Drag";
		}

		@Override
		public String getRedoPresentationName() {
			return "Drag";
		}

		@Override
		public String getUndoPresentationName() {
			return "Drag";
		}

		@Override
		public boolean isSignificant() {
			return true;
		}

		@Override
		public void redo() throws CannotRedoException {
			if (undone){
				for (Map.Entry<DrawingObject, Point> entry:newPositions.entrySet()){
					entry.getKey().setLocation(entry.getValue());
				}
				undone = false;
			}
		}

		@Override
		public boolean replaceEdit(UndoableEdit anEdit) {
			return false;
		}

		@Override
		public void undo() throws CannotUndoException {
			if (!undone){
				for (Map.Entry<DrawingObject, Point> entry:originalPositions.entrySet()){
					entry.getKey().setLocation(entry.getValue());
				}
				undone = true;
			}
		}
		
	}
}
